namespace SimpleWebServices.Web.UI.Html.Elements
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    using SimpleWebServices.Configuration;
    using SimpleWebServices.Data;
    using SimpleWebServices.Globalization;
    using SimpleWebServices.Web.UI.Html.Validation;

    public abstract class CustomElement<T> : Record, IElement, IXmlSerializable
        where T : IElement
    {
        protected CustomElement()
        {
            this.Data = new DataCollection();
            this.ValidatorSettings = new ValidatorSettingsCollection();
        }

        public Names Class
        {
            get;
            set;
        }

        public DataCollection Data
        {
            get;
            set;
        }

        public IElement Element
        {
            get;
            set;
        }

        public override string Entity
        {
            get
            {
                Entity value = new Entity();
                value.Append(this.Class);
                value.Append(this.Data);
                value.Append(this.Element);
                value.Append(this.Id);
                value.Append(this.Language);
                value.Append(this.Name);
                value.Append(this.Type);
                value.Append(this.Title);
                value.Append(this.ValidatorSettings);

                return value;
            }
        }

        public bool Folded
        {
            get;
            set;
        }

        public Name Id
        {
            get;
            set;
        }

        public Language Language
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public string Title
        {
            get;
            set;
        }

        [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The naming is intentionally the same as HTML.")]
        public string Type
        {
            get;
            set;
        }

        public ValidatorSettingsCollection ValidatorSettings
        {
            get;
            set;
        }

        public IElement Deserialize(string name, string type, string xml)
        {
            if (null == xml)
            {
                throw new ArgumentNullException("xml");
            }
            else if (0 == xml.Length)
            {
                throw new ArgumentOutOfRangeException("xml");
            }

            IElement result = null;

            if (string.Equals(type, this.Type, StringComparison.OrdinalIgnoreCase))
            {
                if (string.Equals(name, this.Name, StringComparison.OrdinalIgnoreCase))
                {
                    this.Folded = false;
                    this.Data = GetConfigurationData(xml);
                    result = this.Retrieve();
                }
                else if (string.Equals(name, "fold:" + this.Name, StringComparison.OrdinalIgnoreCase))
                {
                    CustomElement<T> custom = Activator.CreateInstance(this.GetType()) as CustomElement<T>;
                    custom.Folded = true;
                    custom.Data = GetConfigurationData(xml);
                    result = custom;
                }
            }

            return result;
        }

        public string Render()
        {
            string result = null;

            if (this.Folded)
            {
                result = this.Serialize().CreateNavigator().OuterXml;
            }
            else
            {
                if (null != this.Element)
                {
                    result = this.Element.Serialize().CreateNavigator().OuterXml;
                }
            }

            return result;
        }

        public IElement ToElement()
        {
            return this.Element;
        }

        public override string ToString()
        {
            return this.Serialize().CreateNavigator().OuterXml;
        }

        public XmlSchema GetSchema()
        {
            throw new NotSupportedException();
        }

        public void ReadXml(XmlReader reader)
        {
            if (null == reader)
            {
                throw new ArgumentNullException("reader");
            }

            if (reader.IsEmptyElement)
            {
                reader.Read();
            }
            else
            {
                string name = reader.Name;
                reader.Read();
                while (true)
                {
                    if (XmlNodeType.None == reader.NodeType)
                    {
                        break;
                    }
                    else if (XmlNodeType.EndElement == reader.NodeType && reader.Name == name)
                    {
                        reader.Read();
                        break;
                    }
                    else if ("data".Equals(reader.Name, StringComparison.Ordinal))
                    {
                        this.Data = reader.ReadOuterXml().Deserialize<DataCollection>();
                    }
                    else if ("validators".Equals(reader.Name, StringComparison.Ordinal))
                    {
                        this.ValidatorSettings = reader.ReadOuterXml().Deserialize<ValidatorSettingsCollection>();
                    }
                    else
                    {
                        this.Element = HtmlElementConfiguration.Deserialize(reader.Name, reader.GetAttribute("type"), reader.ReadOuterXml()).ToElement();
                    }
                }
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            if (null == writer)
            {
                throw new ArgumentNullException("writer");
            }

            if (this.Folded)
            {
                if (!string.IsNullOrEmpty(this.Type))
                {
                    writer.WriteAttributeString("type", this.Type);
                }
            }
            else
            {
                writer.WriteRaw(this.Element.Render());

                if (null != this.ValidatorSettings)
                {
                    writer.WriteRaw(this.ValidatorSettings.Serialize().CreateNavigator().OuterXml);
                }
            }

            if (null != this.Data && 0 != this.Data.Count)
            {
                writer.WriteRaw(this.Data.Serialize().CreateNavigator().OuterXml);
            }
        }

        public abstract IElement Retrieve();

        protected IElement Unfold(CustomElement<T> custom)
        {
            if (null == custom)
            {
                throw new ArgumentNullException("custom");
            }

            if (null != custom.ValidatorSettings)
            {
                foreach (ValidatorSettings item in custom.ValidatorSettings)
                {
                    item.AddValidator(custom.Element);
                }
            }

            return custom.Element;
        }

        private static DataCollection GetConfigurationData(string xml)
        {
            DataCollection result = null;

            XmlDocument document = new XmlDocument();
            document.LoadXml(xml.Replace("fold:", string.Empty));
            XmlNode data = document.SelectSingleNode("//data[1]");
            if (null != data)
            {
                result = data.OuterXml.Deserialize<DataCollection>();
            }

            return result ?? new DataCollection();
        }
    }
}